
// il - A image library representation.

// Copyright (C) 2005-2006 VM Inc.
// Released under the LGPL.
// For more information, see http://www.openlibraries.org.

#include <algorithm>

#include "il.hpp"

#ifdef HAVE_OPENEXR
#	ifdef WIN32
#		define PLATFORM_WINDOWS
#	endif
#include <ImfConvert.h>
#include <ImathMath.h>
#include <ImathFun.h>
#include <halfFunction.h>
#endif

namespace olib { namespace openimagelib { namespace il {

namespace
{
	void destroy( image_type* im )
	{ delete im; }
}

#ifdef HAVE_OPENEXR

// From ILM OpenEXR examples.
float knee (double x, double f)
{ return float( Imath::Math<double>::log( x * f + 1 ) / f ); }

float findKneeF( float x, float y )
{
    float f0 = 0;
    float f1 = 1;

    while( knee( x, f1 ) > y )
    {
		f0 = f1;
		f1 = f1 * 2;
    }

    for( int i = 0; i < 30; ++i )
    {
		float f2 = ( f0 + f1 ) / 2;
		float y2 = knee( x, f2 );

		if( y2 < y )
			f1 = f2;
		else
			f0 = f2;
    }

    return ( f0 + f1 ) / 2.0f;
}

struct gamma
{
    explicit gamma( float exposure, float defog, float kneeLow, float kneeHigh )
		: m( Imath::Math<float>::pow( 2, exposure + 2.47393f ) )
		, d( defog )
		, kl( Imath::Math<float>::pow( 2, kneeLow ) )
		, f( findKneeF( Imath::Math<float>::pow( 2, kneeHigh ) - kl, Imath::Math<float>::pow( 2, 3.5 ) - kl ) )
	{ }

    float operator( )( half h )
    {
		// Defog
		float x = std::max( 0.0f, ( h - d ) );

		// Exposure
		x *= m;

		// Knee
		if( x > kl )
			x = kl + knee( x - kl, f );

		// Gamma
		x = Imath::Math<float>::pow( x, 0.4545f );

		// Scale and clamp
		return Imath::clamp( x * 84.66f, 0.f, 255.f );
    }
    
    float m, d, kl, f;
};

unsigned char dither( float v, int x, int y )
{
    static const float d[ 4 ][ 4 ] =
    {
		0.0f  / 16.0f,  8.0f / 16.0f,  2.0f / 16.0f, 10.0f / 16.0f,
		12.0f / 16.0f,  4.0f / 16.0f, 14.0f / 16.0f,  6.0f / 16.0f,
		3.0f  / 16.0f, 11.0f / 16.0f,  1.0f / 16.0f,  9.0f / 16.0f,
		15.0f / 16.0f,  7.0f / 16.0f, 13.0f / 16.0f,  5.0f / 16.0f,
    };

    return ( unsigned char ) ( v + d[ y & 3 ][ x & 3 ] );
}

void compute_fog_color( const image_type_ptr& im, int bs, float& fog_r, float& fog_g, float& fog_b )
{
	fog_r = fog_g = fog_b = 0.0f;
	
	int i_block_inc = sizeof( unsigned short ) * bs;
	
	image_type::const_pointer src_im = im->data( );
	for( int i = 0; i < im->height( ); ++i )
	{
		for( int j = 0; j < im->width( ); ++j )
		{
			for( int k = 0; k < bs; ++k )
			{
				half h;
			
				h.setBits( ( ( unsigned short* ) src_im )[ 0 ] );
				if( h.isFinite( ) )
					fog_r += h;
			
				src_im += sizeof( unsigned short );
			}
		}
	}
	
	fog_r /= im->width( ) * im->height( );
	fog_g /= im->width( ) * im->height( );
	fog_b /= im->width( ) * im->height( );
}

// GC - need to move to utility.
image_type_ptr convert_to_half( const image_type_ptr& im, int& bs )
{
	typedef il::image<unsigned char, il::r16g16b16f>	r16g16b16f_image_type;
	typedef il::image<unsigned char, il::r16g16b16a16f>	r16g16b16a16f_image_type;

	if( !( im->pf( ) == L"r32g32b32f" || im->pf( ) == L"r32g32b32a32f" ) )
	{
		if( im->pf( ) == L"r16g16b16f" )
			bs = 3;
		else if( im->pf( ) == L"r16g16b16a16f" )
			bs = 4;

		return im;
	}
	
	image_type_ptr new_im;
	if( im->pf( ) == L"r32g32b32f" )
	{
		new_im = image_type_ptr( new image_type( r16g16b16f_image_type( im->width( ), im->height( ), 1 ) ), destroy );
		bs = 3;
	}
	else if( im->pf( ) == L"r32g32b32a32f" )
	{
		new_im = image_type_ptr( new image_type( r16g16b16a16f_image_type( im->width( ), im->height( ), 1 ) ), destroy );
		bs = 4;
	}
	
	image_type::const_pointer src_im = im->data( );
	image_type::pointer		  dst_im = new_im->data( );
		
	int f_block_inc = sizeof( float ) * bs;
	int i_block_inc = sizeof( unsigned short ) * bs;
		
	for( int i = 0; i < im->height( ); ++i )
	{
		for( int j = 0; j < im->width( ); ++j )
		{
			for( int k = 0; k < bs; ++k )
				( ( unsigned short* ) dst_im )[ k ] = Imf::floatToHalf( ( ( const float* ) src_im )[ k ] ).bits( );
				
			src_im += f_block_inc;
			dst_im += i_block_inc;
		}
	}
	
	return new_im;
}

image_type_ptr ilm_exr_tonemap( const image_type_ptr& im, float exposure, float defog, float kneeLow, float kneeHigh )
{
	typedef il::image<unsigned char, il::l8>		l8_image_type;
	typedef il::image<unsigned char, il::r8g8b8>	r8g8b8_image_type;
	typedef il::image<unsigned char, il::r8g8b8a8>	r8g8b8a8_image_type;

	if( !( im->pf( ) == L"r16g16b16f"	 ||
		   im->pf( ) == L"r16g16b16a16f" ||
		   im->pf( ) == L"r32g32b32f"	 ||
		   im->pf( ) == L"r32g32b32a32f" ) )
		return image_type_ptr( );
	
	int bs;
	image_type_ptr half_im = convert_to_half( im, bs );
	
	float fog_r, fog_g, fog_b;
	compute_fog_color( half_im, bs, fog_r, fog_g, fog_b );

	halfFunction<float> r_gamma( gamma( exposure, defog * fog_r, kneeLow, kneeHigh ), -HALF_MAX, HALF_MAX );
	halfFunction<float> g_gamma( gamma( exposure, defog * fog_g, kneeLow, kneeHigh ), -HALF_MAX, HALF_MAX );
	halfFunction<float> b_gamma( gamma( exposure, defog * fog_b, kneeLow, kneeHigh ), -HALF_MAX, HALF_MAX );

	image_type_ptr new_im;
	if( bs == 3 )
		new_im = image_type_ptr( new image_type( r8g8b8_image_type( im->width( ), im->height( ), 1 ) ), destroy );
	else if( bs == 4 )
		new_im = image_type_ptr( new image_type( r8g8b8a8_image_type( im->width( ), im->height( ), 1 ) ), destroy );

	image_type::const_pointer src_im = half_im->data( );
	image_type::pointer		  dst_im = new_im->data( );

	for( int i = 0; i < new_im->height( ); ++i )
	{
		for( int j = 0; j < new_im->width( ); ++j )
		{
			for( int k = 0; k < bs; ++k )
			{
				half h;
			
				h.setBits( ( ( unsigned short* ) src_im )[ 0 ] );
			
				*dst_im = dither( r_gamma( h ), j, i );
			
				src_im += sizeof( unsigned short );
				dst_im += sizeof( unsigned char );
			}
		}
	}	

	return new_im;
}
#endif

} } }
